package client;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Arrays;


import config.Config;
import config.FullMiddleManConfiguration;

// TODO: This would be more appropriately be called Experiment.
public class ClientMain {
	
	private int clientsCount;
	private int duration;
	private String outFilename;
	private Client [] clients;
	private FullMiddleManConfiguration[] middleMenConfig; 

	public ClientMain(String _outFilename, int _clientCount, int _duration, FullMiddleManConfiguration []_middleMenConfig) {
		
		outFilename = _outFilename;
		clientsCount = _clientCount;
		duration = _duration;
		middleMenConfig = _middleMenConfig.clone();
		
		String liveFilename = outFilename.substring(outFilename.lastIndexOf('/')+1);
		liveFilename = liveFilename.substring(0, liveFilename.length()-4) + "-live.txt";
		liveFilename = "/local/group3705/" + liveFilename;
		System.out.println("LIVE FILENAME AT: " + liveFilename);
		LiveFileWriter.Start(liveFilename);
	}
	
	public static void main(String[] args) {
		if(args.length < 3) {
			System.out.println("Please use arguments:");
			System.out.println("Concerning the client side:");
			System.out.println("\t<arg1>: # of clients threads");
			System.out.println("\t<arg2>: minutes to run the experiment for");
			System.out.println("\t<arg3>: results file");
			System.out.println("\t<arg4>: # of middle men");
			System.out.println("\t<arg5>: configuration strings for middle man 1");
			System.out.println("\t[arg6]: configuration strings for middle man 2");
			System.out.println("\t...");
			return ;
		}
		
		int clientsCount = Integer.parseInt(args[0]);
		int duration = Integer.parseInt(args[1]);
		String outFilename = args[2];
		int middle_men = Integer.parseInt(args[3]);
		
		FullMiddleManConfiguration []middleMenConfig = new FullMiddleManConfiguration[middle_men];
		for(int i=0; i<middle_men; i++) {
			try {
				middleMenConfig[i] = new FullMiddleManConfiguration(args[i+4]);
			} catch(IllegalArgumentException e){
				System.err.println("Illegal argument "+i+3+": \n" + e.getMessage());
				return;
			}
			if(middleMenConfig[i].getMiddleManConfiguration().P() != middleMenConfig[i].getMiddleManConfiguration().db_hosts().length) {
				System.err.println("For middle man " + i + " (" + middleMenConfig[i].middleManHost() + ") P ("+middleMenConfig[i].getMiddleManConfiguration().P()+") is not equal to # of databases ("+middleMenConfig[i].getMiddleManConfiguration().db_hosts().length+")");
				return;
			}
		}
		
		ClientMain m = new ClientMain(outFilename, clientsCount, duration, middleMenConfig );

		m.runExperiment();
	}

	private void prepareExperiment() {
		sendParameters();
		
		// Give the Middle Man some time to initialize itself
		// 5 seconds
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
		}
		
		clients = new Client[clientsCount];
		
		int currentMiddleMan = 0;
		for(int i=0; i<clients.length; i++) {
			clients[i] = new Client(duration, i, middleMenConfig[currentMiddleMan].middleManHost());
			currentMiddleMan = (currentMiddleMan+1) % middleMenConfig.length;
		}

	}
	
	private void terminateExperiment() {
		for(int i=0; i<middleMenConfig.length; i++) {
			PrintWriter out = null;
			BufferedReader in = null;
			String cbuf;
			Socket middle_man;
			
			try {
				middle_man = new Socket(middleMenConfig[i].middleManHost(), Config.middle_man_setup_port);
				out = new PrintWriter(middle_man.getOutputStream(), true);
				in = new BufferedReader(new InputStreamReader(middle_man.getInputStream()));
				out.println("TERMINATE");
				System.out.println("Terminate sent");
				processResults();
					// We should receive "OK"
				System.out.println("Waiting!");
				cbuf = in.readLine();
				System.out.println("Reply:  " + cbuf);
				in.close();
				out.close();
				middle_man.close();
			} catch (UnknownHostException e) {
				System.err.println("MAIN Client could not connect: Unknown host " + middleMenConfig[i].middleManHost());
				e.printStackTrace();
			} catch (IOException e) {
				System.err.println("MAIN Client could not connect: IO Error");
				e.printStackTrace();
			}
		}
	}
	
	public void runExperiment() {
		prepareExperiment();
		
		for(int i=0; i<clients.length; i++) {
			clients[i].start();
		}
		
		long timeStart = System.currentTimeMillis();
		do {
			long timeStop = System.currentTimeMillis();
			
			if(timeStop - timeStart < duration * 60 * 1000) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// just sleep
				}
			} else {
				break;
			}
		} while(true);
		
		for(int i=0; i<clients.length; i++) {
			try {
				clients[i].getSocket().close();
			} catch (IOException e){
				// do nothing, this is mean code
			}
		}
		
		for(int i=0; i<clients.length; i++) {
			try {
				clients[i].join();
			} catch (InterruptedException e) {
				System.out.println("Client "+i+" was interrupted:");
				e.printStackTrace();
			}
		}
		
		LiveFileWriter.getInstance().Finish();
		
		terminateExperiment();
	}

	public void processResults() {
		Measurement []results;
		int total_results = 0;
		
		for(int i=0; i<clients.length; i++) {
			total_results += clients[i].getResult().size();
		}
		
		results = new Measurement[total_results];
		total_results = 0; 
		for(int i=0; i<clients.length; i++) {
			for(Measurement r : clients[i].getResult()) {
				results[total_results++] = r;
			}
		}
		
		Arrays.sort(results);
		
		try {
			FileWriter fstream = new FileWriter(outFilename,false);
			BufferedWriter outFile = new BufferedWriter(fstream);
			for(Measurement r : results) {
				outFile.write(r.toString());
				outFile.newLine();
			}
			outFile.close();
		} catch (Exception e) {
			System.out.println("Can't write to experiment file!!!");
			e.printStackTrace();
		}
	}

	private void sendParameters() {
		System.err.println("SENDING PARAMETERS");
		for(int i=0; i<middleMenConfig.length; i++) {
			PrintWriter out = null;
			Socket middle_man;
			System.err.println("SENDING PARAMETERS FOR "+middleMenConfig[i].middleManHost());
			try {
				middle_man = new Socket(middleMenConfig[i].middleManHost(), Config.middle_man_setup_port);
				out = new PrintWriter(middle_man.getOutputStream(), true);
				out.println("EXPERIMENT" + middleMenConfig[i].getMiddleManConfiguration().toString());
				out.close();
				middle_man.close();
			} catch (UnknownHostException e) {
				System.err.println("MAIN Client could not connect: Unknown host " + middleMenConfig[i].middleManHost());
				e.printStackTrace();
			} catch (IOException e) {
				System.err.println("MAIN Client could not connect: IO Error");
				e.printStackTrace();
			}
		}
	}
}
